package model;

import java.util.HashMap;
import java.util.Map;

public class Marking {
	
	private final HashMap<Place, Integer> markings;
	
	public Marking() {
		this.markings = new HashMap<Place, Integer>();
	}
	
	/**
	 * Adds a new {@link Place} with the provided amount if the {@link Place} is not already
	 * in this {@link Marking}.
	 * Increments the amount of the provided {@link Place}  if the {@link Place} is already in 
	 * this {@link Marking}.
	 * If the amount is 0, nothing is done.
	 * If the new amount is 0, place is removed from this {@link Marking}.
	 * @param place is the {@link Place} of which the amount should be increased.
	 * @param amount is the value by which the amount is increased.
	 */
	public void add(final Place place, final int amount) {
		if(amount != 0){
			if(this.markings.containsKey(place)){
				final int newAmount = this.markings.get(place) + amount;
				if(newAmount == 0){
					this.markings.remove(place);
				} else {
					this.markings.put(place, newAmount);
				}
			} else {
				this.markings.put(place, amount);
			}
		}
	}
	
	/**
	 * Subtracts the value of the provided {@link Place} by the the provided amount 
	 * if the {@link Place} is in this {@link Marking}.
	 * If the {@link Place} is not in this {@link Marking},
	 * the {@link Place} is added with negative the provided amount as the amount.
	 * If the new amount after subtraction is 0, the {@link Place} is removed from this {@link Marking}.
	 * @param place is the {@link Place} of which the amount should be decreased.
	 * @param amount is the value by which the amount is subtracted.
	 */
	private void subtract(final Place place, final Integer amount) {
		if(amount != 0){
			int newAmount;
			if(this.markings.containsKey(place)){
				newAmount = this.markings.get(place) - amount;
			} else {
				newAmount = 0 - amount;
			}
			this.markings.put(place, newAmount);
		}
	}
	
	/**
	 * Creates a new {@link Marking} with the markings in minuend subtracted by the markings in subtrahend.
	 * Does not alter the minuend.
	 * @param minuend is the minuend.
	 * @param subtrahend is the subtrahend.
	 * @return a new {@link Marking}.
	 */
	public static Marking subtract(final Marking minuend, final Marking subtrahend) {
		final Marking result = new Marking();
		result.add(minuend);
		result.subtract(subtrahend);
		return result;
	}
	
	/**
	 * Creates a new {@link Marking} with the markings in firstMarking combined withe the markings in secondMarking.
	 * Does not alter the provided {@link Marking}s.
	 * @param firstMarking is the first {@link Marking} to add.
	 * @param secondMarking is the second {@link Marking} to add.
	 * @return a new {@link Marking}.
	 */
	public static Marking add(final Marking firstMarking, final Marking secondMarking) {
		final Marking result = new Marking();
		result.add(firstMarking);
		result.add(secondMarking);
		return result;
	}
	
	/**
	 * Adds all the markings in the provided {@link Marking} to this {@link Marking}.
	 * @param marking is the {@link Marking} which contains markings that should be added to this {@link Marking}.
	 */
	public void add(final Marking marking) {
		for (final Place place : marking.markings.keySet()) {
			this.add(place, marking.markings.get(place));
		}
	}
	
	/**
	 * Subtracts all the markings in the provided {@link Marking} form this {@link Marking}.
	 * @param marking is the {@link Marking} which contains markings that should be removed from this {@link Marking}.
	 */
	private void subtract(final Marking marking) {
		for (final Map.Entry<Place, Integer> placeEntry : marking.markings.entrySet()) {
			this.subtract(placeEntry.getKey(), placeEntry.getValue());
		}
	}

	/**
	 * Provides true of all markings in this {@link Marking} have an amount greater than or equal to 0.
	 * @return true if all markings in the {@link Marking} have an amount greater than or equal to 0. Else false.
	 */
	public boolean greaterEqualZeroVector() {
		for (final Place place : this.markings.keySet()) {
			if(this.markings.get(place) < 0){
				return false;
			}
		}
		return true;
	}

	public boolean containsSubmarkingOf(final Marking marking) {
		if(!this.equals(marking)){
			for (final Map.Entry<Place,Integer> placeEntry : this.markings.entrySet()) {
				if(marking.contains(placeEntry.getKey())){
					if(placeEntry.getValue() > marking.getValue(placeEntry.getKey())){
						return false;
					}
				} else {
					return false;
				}
			}
			return true;
		} else {
			return false;
		}
	}
	
	public int getValue(final Place place) {
		if(this.markings.containsKey(place)){
			return this.markings.get(place);
		} else {
			return 0;
		}
	}

	public void setValue(final Place place, final int value) {
		this.markings.remove(place);
		this.add(place, value);
	}
	
	public boolean contains(final Place place) {
		return this.markings.containsKey(place);
	}	
	
	@Override
	public String toString(){
		return this.markings.toString();
	}

	@Override
	public boolean equals(final Object obj){
		if(obj instanceof Marking){
			return this.markings.equals(((Marking) obj).markings);
		}
		return false;
	}
}